`include "define.svh"

module alu(
    input wire [`REG_WIDTH - 1 : 0]     op1_i,
    input wire [`REG_WIDTH - 1 : 0]     op2_i,
    input wire [`ALUOP_WIDTH - 1 : 0]   aluop_i,
    
    output reg [`REG_WIDTH - 1 : 0]     rlt_o,
    output reg                          overflow_o,
    
    // MULT instructions related.
    output reg [`REG_WIDTH - 1 : 0]     whidata_o,
    output reg [`REG_WIDTH - 1 : 0]     wlodata_o
    
);

    // ADD_ALU
    wire [32 : 0] ext_op1 = {op1_i[31], op1_i};
    wire [32 : 0] ext_op2 = {op2_i[31], op2_i};
    wire [32 : 0] add_tmp_rlt = $unsigned(ext_op1) + $unsigned(ext_op2);
    wire [31 : 0] add_rlt = add_tmp_rlt[31 : 0];
    wire add_overflow = (add_tmp_rlt[32] ^ add_tmp_rlt[31]);
    
    // ADDU_ALU
    wire [31 : 0] addu_rlt = $unsigned(op1_i) + $unsigned(op2_i);
    
    // SUB_ALU
    wire [32 : 0] sub_tmp_rlt = $unsigned(ext_op1) - $unsigned(ext_op2);
    wire [31 : 0] sub_rlt = sub_tmp_rlt[31 : 0];
    wire sub_overflow = (sub_tmp_rlt[32] ^ sub_tmp_rlt[31]);
    
    // SUBU_ALU
    wire [31 : 0] subu_rlt = $unsigned(op1_i) - $unsigned(op2_i);
    
    // LessThan_ALU
    wire lt_rlt = ($signed(op1_i) < $signed(op2_i)) ? 32'b1 : 32'b0;
    
    // LessThanU_ALU
    wire ltu_rlt = ($unsigned(op1_i) < $unsigned(op2_i)) ? 32'b1 : 32'b0;
    
    // MULT_ALU & MULTU_ALU
    wire [63 : 0] mul_rlt = $signed(op1_i) * $signed(op2_i);
    wire [63 : 0] mulu_rlt = $unsigned(op1_i) * $unsigned(op2_i);
    reg [31 : 0] mul_hi_rlt;
    reg [31 : 0] mul_lo_rlt;
    always_comb begin
        if (aluop_i == `MUL_ALU) begin
            mul_hi_rlt = mul_rlt[63 : 32];
            mul_lo_rlt = mul_rlt[31 : 0];
        end else begin
            mul_hi_rlt = mulu_rlt[63 : 32];
            mul_lo_rlt = mulu_rlt[31 : 0];
        end
    end
    
    // LOGIC
    reg [31 : 0] logic_rlt;
    always_comb begin
        case (aluop_i)
            `AND_ALU:   logic_rlt = op1_i & op2_i;
            `NOR_ALU:   logic_rlt = ~(op1_i | op2_i);
            `OR_ALU:    logic_rlt = op1_i | op2_i;
            `XOR_ALU:   logic_rlt = op1_i ^ op2_i;
            `SLL_ALU:   logic_rlt = op2_i << op1_i[4 : 0];
            `SRA_ALU:   logic_rlt = $signed(op2_i) >>> op1_i[4 : 0];
            `SRL_ALU:   logic_rlt = op2_i >> op1_i[4 : 0];
            `LUI_ALU:   logic_rlt = op1_i;
            default:    logic_rlt = `ZERO_REG;
        endcase
    end
    
    
    
    /* ********** Output signal ********** */
    // rlt_o
    always_comb begin
        case (aluop_i)
            `ADD_ALU:       rlt_o = add_rlt;
            `ADDU_ALU:      rlt_o = addu_rlt;
            `SUB_ALU:       rlt_o = sub_rlt;
            `SUBU_ALU:      rlt_o = subu_rlt;
            `LessThan_ALU:  rlt_o = lt_rlt;
            `LessThanU_ALU: rlt_o = ltu_rlt;
            `AND_ALU, `NOR_ALU, `OR_ALU, `XOR_ALU,
            `SLL_ALU, `SRA_ALU, `SRL_ALU, `LUI_ALU:
                            rlt_o = logic_rlt;
            default:        rlt_o = `ZERO_REG;
        endcase
    end
    // overflow_o
    always_comb begin
        case (aluop_i)
            `ADD_ALU:       overflow_o = add_overflow;
            `SUB_ALU:       overflow_o = sub_overflow;
            default:        overflow_o = 1'b0;
        endcase
    end
    // whidata_o & wlodata_o
    always_comb begin
        case (aluop_i)
            `MUL_ALU, `MULU_ALU: begin
                whidata_o = mul_hi_rlt;
                wlodata_o = mul_lo_rlt;
            end
            default: begin
                whidata_o = `ZERO_REG;
                wlodata_o = `ZERO_REG;
            end
        endcase
    end
    
endmodule
